package com.zazu.mycl.application;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;

import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.text.BadLocationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import javax.swing.JFrame;

import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

import com.zazu.mycl.application.commands.ThreadableCommand;

public class DefaultCommandRunner implements CommandRunner{


    public DefaultCommandRunner(Document mappings, JTextArea aTextArea, JFrame comWindow)
	{
		this.commandWindow = comWindow;
                this.mappings = mappings;
		this.textArea = aTextArea;
		XPathFactory xpFactory = XPathFactory.newInstance();
		path = xpFactory.newXPath();
		setVariable(constants.CURRENT_DIRECTORY, "/");
		threadPool = Executors.newCachedThreadPool();
		commandHistory.ensureCapacity(100);
	}
    
    public JFrame getCommandWindow()
    {
        return this.commandWindow;
    }
	
	public void lockInput()
	{
		
	}
	
	public String getCommandClass(String commandName)
	{
		try {
			NodeList classMaps = (NodeList) path.evaluate("/mappings/map", mappings, XPathConstants.NODESET);
			
			int nodeCount = classMaps.getLength();
			
			for(int i = 0; i <= nodeCount; i++)
			{
				String curCommandName = path.evaluate("@command", classMaps.item(i));
				
				if( curCommandName.equals(commandName)  )
				{
					String className = path.evaluate("@class", classMaps.item(i));
					return className;
				}
			}
			
			return null;
			
		} catch (XPathExpressionException e) {
			return null;
		}
		
	}
	
	private String[] regroupQuoted(String[] args)
	{
		String[] newArray = new String[args.length];
		
		StringBuilder sb;
		
		boolean haveBeggining = false;
		
		int j = 0;
		
		for(int i = 0; i <= args.length-1; j++)
		{
			
			boolean done = false;
			sb = new StringBuilder("");
			
			
				while(done == false)
				{
					
					//ApplicationController.logger.log(Level.INFO, "Running while...");
					
					if(i >= args.length)
					{
						newArray[j] = sb.toString();
						done = true;
					}
					else if(haveBeggining == true && i == args.length && !args[i].endsWith("\""))
					{
						newArray[j] = sb.toString();
						done = true;
					}
					else if(haveBeggining == true && args[i].endsWith("\""))
						{
							//ApplicationController.logger.log(Level.INFO, "Have end: "+args[i].substring(0, args[i].length() - 1));
							sb.append(args[i].substring(0, args[i].length() - 1));
							haveBeggining = false;
							
							//ApplicationController.logger.log(Level.INFO, "1: Adding item: "+sb.toString());
							newArray[j] = sb.toString();
							
							done = true;
						}
						else if(haveBeggining == true && !args[i].endsWith("\""))
						{
							//ApplicationController.logger.log(Level.INFO, "Have middle: "+args[i]);
							sb.append(args[i]+" ");
						}
						else if(args[i].startsWith("\""))
						{
							//ApplicationController.logger.log(Level.INFO, "Have beginning: "+args[i].substring(1,args[i].length()));
							sb.append(args[i].substring(1,args[i].length())+" ");
							haveBeggining = true;
						}
						else
						{
							//ApplicationController.logger.log(Level.INFO, "Have item: "+args[i]);
							sb.append(args[i]);
							haveBeggining = false;
							
							//ApplicationController.logger.log(Level.INFO, "2: Adding item: "+sb.toString());
							newArray[j] = sb.toString();
							
							done = true;
						}	
					i++;
					}
		}
		
		String[] finalArray = Arrays.copyOf(newArray, j);
		return finalArray;
	}
	
	public void upPressed()
	{
		printHistoryLine();
	}
	
	public void downPressed()
	{
		printPreviousHistoryLine();
	}
	
	private void printHistoryLine()
	{
		if((commandHistory.size() -1) - (this.historyLine) >= 0)
		{
			String hl =	this.commandHistory.get((commandHistory.size()-1) - this.historyLine);
			replaceLastLine(hl);
			
			if(historyLine + 1 <= commandHistory.size()-1)
			{
				historyLine++;
			}
		
		System.out.println("Up: "+historyLine);
		
		}
	}
	
	private void printPreviousHistoryLine()
	{
		if(historyLine - 1 >= 0)
		{
			if(historyLine != 0)
				{
					historyLine--;
				}
				
				String hl =	this.commandHistory.get((commandHistory.size()-1) - this.historyLine);
				replaceLastLine(hl);
				
				System.out.println("Down: "+historyLine);
		}
	}
	
	private void addHistoryLine(String line)
	{
		commandHistory.push(line);
	}
	
	private void resetHistoryLine()
	{
		historyLine = 0;
	}
	
	public void runCommand(String CommandLine) {
		
		if(CommandLine.equals(""))
		{
			//Delete the last line
			return;
		}
		
		addHistoryLine(CommandLine);
		
		String currentOutputFile = null;
		
		//Parse command string and get args
		String[] commandSep = CommandLine.split(">>");
		
		String[] args = commandSep[0].split(" ");
		
		String CommandName = args[0];
		
		args = regroupQuoted(args);
		
		this.SetLine("");
		
		if(activeCommand != null)
		{
			ApplicationController.logger.log(Level.INFO,"Sending subcommand: "+CommandName);
			activeCommand.runSubCommand(args, CommandLine);
			return;
		}
		
		
		String className = getCommandClass(CommandName);
		
		try
		{
			currentOutputFile = commandSep[1];
			ApplicationController.logger.log(Level.INFO,"Changing output file to: "+commandSep[1]);
		}
		catch(Exception e)
		{
			//No output file -- fine!
			ApplicationController.logger.log(Level.INFO,"No output file specified.");
		}
		
		if(className != null)
		{
			
				try {
				
				
					terminalCommand myRunner;
					
					ApplicationController.logger.log(Level.INFO,"Instantiating class: "+className+". For command: "+CommandName);
					myRunner = (terminalCommand) Class.forName(className).newInstance();
					
					if(myRunner.isThreadable())
					{
						threadPool.submit(new ThreadableCommand(args, this, CommandLine, currentOutputFile, myRunner));
						ApplicationController.logger.log(Level.INFO,"Running command as Thread: "+className+". For command: "+CommandName);
					}
					else
					{
						myRunner.run(args, this, CommandLine, currentOutputFile);
						ApplicationController.logger.log(Level.INFO,"Running command in same thread: "+className+". For command: "+CommandName);
						
					}
					
				
				
			} catch (InstantiationException e) {
				ApplicationController.logger.log(Level.WARNING,"Error instantiating class: "+className);
				this.SetLine("Error running command.");
			} catch (IllegalAccessException e) {
				ApplicationController.logger.log(Level.WARNING,"Error running terminal command: "+className);
				this.SetLine("Error running command.");
			} catch (ClassNotFoundException e) {
				ApplicationController.logger.log(Level.SEVERE,"No class found for command: "+CommandName);
				this.SetLine("Error running command: class not found.");
			}
			finally
			{
				resetHistoryLine();
			}
		}
		else
		{
			//No Command
			ApplicationController.logger.log(Level.INFO,"No command found for: "+CommandName);
			this.SetLine("No command found for: "+CommandName);
		}
		}

	
	public String Ask(String Question) {
		// TODO Auto-generated method stub
		return null;
	}

	
	public void EnterPressed()
	{
		//inputArea.getLineCount();
		runCommand(getLastLine());
	}
	
	public void RelinqishControll() {
		this.activeCommand = null;
	}

	public void replaceLastLine(String line)
	{
		
		try {
			int lineNum = this.textArea.getLineCount()-1;
			textArea.replaceRange(line, textArea.getLineStartOffset(lineNum), textArea.getLineEndOffset(lineNum));
			goToEnd();
		} catch (BadLocationException e) {
			ApplicationController.logger.log(Level.SEVERE, "Error replacing last line.");
		}
	}
	
	public void SetLine(String line) 
	{	
		this.textArea.append(line+"\n");
		goToEnd();
	}
	
	public void goToEnd()
	{
		try {
			this.textArea.setCaretPosition(this.textArea.getLineEndOffset(this.textArea.getLineCount()-1));
		} catch (BadLocationException e) {
			ApplicationController.logger.log(Level.SEVERE, "Error setting caret position.");
		}
	}
	
	public void AppendText(String line) 
	{	
		this.textArea.append(line);
			
		//ApplicationController.logger.log(Level.WARNING, "Error changing last line in text area. Error: "+e.getMessage());
		
	}
	
	public void SetLine(JPanel line) 
	{
		//TODO implement
	}

	public String getLastLine()
	{
		
			int LineCount = this.textArea.getLineCount()-1;
			
			int LineEnd;
			int LineStart;
			String Line;
			
			
			try {
				LineEnd = this.textArea.getLineEndOffset(LineCount);
				LineStart = this.textArea.getLineStartOffset(LineCount);
				Line = this.textArea.getText().substring(LineStart, LineEnd);
				return Line;
				
			} catch (BadLocationException e) {
				ApplicationController.logger.log(Level.WARNING, "Error selecting line in text area.");
				return null;
			}
		
	}
	
	public void TakeControll(terminalCommand command) {
		this.activeCommand = command;
	}
	
	public String getVariable(String name) {

		String val = envVars.get(name);
		
		if(val != null)
		{
			return val;
		}
		else
		{
			return null;
		}
	}

	public void setVariable(String name, String value) 
	{
		envVars.put(name, value);
	}
	
	private int historyLine = 0;
	private ExecutorService threadPool;
	private String activeCommandName;
	private terminalCommand activeCommand;
	private XPath path;
	private Document mappings;
	private Stack<String> commandHistory = new Stack<String>();
	private Map<String,String> envVars = new HashMap<String,String>();
	private JTextArea textArea;
        private JFrame commandWindow;
}
